/*
 *Copyright (c) 2024 Black Sesame Technologies
 *
 *Licensed under the Apache License, Version 2.0 (the "License");
 *you may not use this file except in compliance with the License.
 *You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 *Unless required by applicable law or agreed to in writing, software
 *distributed under the License is distributed on an "AS IS" BASIS,
 *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *See the License for the specific language governing permissions and
 *limitations under the License.
*/

/**
 * @file  ipc_trans_impl.c
 * @brief This file provides the implementation for the IPC transfer protocol
 * compatibility layer. Refer to ../include/ipc_trans_layer.h for the API
 * definitions to implement, and utilize the APIs available in
 * ./src/ipc_runtime.h as needed.
 * @details
 * feature list
 * 1. stub api implementation
 * 2. proxy api implementation
 * 3. state management process
 */
#include "../src/ipc_trans_runtime.h"
#include "bst_ipc_config.h"
#include "ipc_trans_layer.h"
#include <pthread.h>
#include <stdatomic.h>
#include <unistd.h>

/* performance optimization spec start */
#if !defined(BAREMETAL_VERSION_TRUNCATE)
typedef struct {
	sem_t sem;
} completion_t;
#endif

// device info buffer management
msgbx_end_device_t dev_info;

void check_module_info() {
	msgbx_end_device_t *module = NULL;
    module = (msgbx_end_device_t *)&dev_info;
    for(int i=0;i<CHANNEL_COUNT;i++) {
        for(int j=0;j<SESSION_COUNT;j++) {
            hprintf(DEBUG, "module:0x%x i:%d j:%d fid:%d sid:%d cid:%d status:%d role:%d\n",
            module, i,j,
                module->ses_map[i][j].info.fid,
                module->ses_map[i][j].info.sid,
                module->ses_map[i][j].info.cid,
                module->ses_map[i][j].status,
                module->ses_map[i][j].info.role
                );
        }
    }
}

#if !defined(BAREMETAL_VERSION_TRUNCATE)
static completion_t recv_comp[CHANNEL_COUNT][SESSION_COUNT];
static completion_t sts_comp;
static volatile bool session_exit[CHANNEL_COUNT][SESSION_COUNT];
#endif
int32_t ipc_trans_complete(const uint8_t cpuid, const uint8_t ses_id);
int32_t ipc_trans_complete_sts(const uint8_t cpuid);

#if !defined(BAREMETAL_VERSION_TRUNCATE)
static void completion_init(completion_t *comp)
{
	sem_init(&comp->sem, 0, 0);
}

static void complete(completion_t *comp)
{
	sem_post(&comp->sem);
}

static void wait_for_completion(completion_t *comp)
{
	sem_wait(&comp->sem);
}
#endif

/* Note: performance optimization spec function, if you do not need any
 * completion, you can return 0 directly. */
int32_t ipc_trans_complete(const uint8_t cpuid, const uint8_t ses_id)
{
#if !defined(BAREMETAL_VERSION_TRUNCATE)
	uint8_t fid, sid;
	session_dist(ses_id, &sid, &fid);
	complete(&recv_comp[fid][sid]);
#endif
	return 0;
}
/* performance optimization spec end */

/* central end status monitor spec start */
/* Note: central end status monitor spec function, if you do not need any
 * completion, you can return 0 directly. */
int32_t ipc_trans_complete_sts(const uint8_t cpuid)
{
#if !defined(BAREMETAL_VERSION_TRUNCATE)
	complete(&sts_comp);
#endif
	return 0;
}

/*
   error message process, call ipc_trans_err_hdl to pass fault handle command.
   handle command is TBD
*/
int err_func(void *addr, msgbx_err_msg_t *err_msg)
{
	IPC_LOG_INFO("recv err msg, err_msg.type = %d, id = %d, msg = %d ",
		     err_msg->type, err_msg->id, err_msg->msg);
#ifdef IPC_STATE_MGT_ENABLE
	ipc_trans_err_hdl(err_msg->type, err_msg->id, 0, addr);
#endif
	IPC_LOG_INFO("ipc_trans_err_hdl success!!");
	return 0;
}
/**
 * @name start ipc driver
 * @attention only driver api
 * @details
 * 1. MsgBx hw layer init
 * 2. register receive message callback function
 * 3. set state management configuration, if necessary
 * NOTE:
 * 1. role in here is just a special demo MUST parameter.
 * 2. This API is invoked multiple times per session. To support multiple sessions for the msgbx driver, implement the
 * following function once during driver initialization. Subsequent calls to this API by the app should return 0
 * directly.
 */
int32_t ipc_trans_layer_start(const uint8_t endid, uint8_t role)
{
	int32_t ret = -1;
	
	IPC_LOG_INFO("sizeof (msgbx_end_device_t %ld)",
		     sizeof(msgbx_end_device_t));
#ifdef IPC_STATE_MGT_ENABLE
	ret = ipc_trans_init(role, err_func, &dev_info);
#else
	ret = ipc_trans_init(role, NULL, &dev_info);
#endif
	if (ret != 0)
		IPC_LOG_WARNING("ipc_trans_init error");
		
	return ret;
}

/**
 * @name stop ipc driver
 * @attention only driver api
 * @details
 * NOTE:
 * 1. This API is invoked multiple times per session.To support multiple sessions for the msgbx driver,implement the
 * following function once during driver initialization. Subsequent calls to this API by the app should return 0
 * directly.
 */
int32_t ipc_trans_layer_stop(const uint8_t endid)
{
	// transfer endid to device info buffer address
	return ipc_trans_deinit(&dev_info);
}

/**
 * @name get info change
 * @attention both client and server api
 * @details
 * 1. query available message handle and type
 * 2. query any end status changed
 * NOTE: type is reserved, in this demo, we do not pass type information to
 * upper layer.
 * @return
 * ret < 0 means do not have available info changed, you should still wait or do other process. ret > 0 means end
 * status change ret = 0 means get receiving message
 */

/* Note: performance optimization spec implementation, please refer to
 * programmers' guide */
int32_t ipc_trans_layer_query_info(const uint8_t endid, const uint8_t handle)
{
	uint8_t type = 0;
#if !defined(BAREMETAL_VERSION_TRUNCATE)

	uint8_t fid = 0, sid = 0;
	int32_t ret = -1;
	session_dist(handle, &sid, &fid);
	ret = ipc_trans_get_avail_info(handle, &type, &dev_info);
	while (ret < 0 && !session_exit[fid][sid]) {
		wait_for_completion(&recv_comp[fid][sid]);
		ret = ipc_trans_get_avail_info(handle, &type, &dev_info);
	}
	return ret;
#else
	return ipc_trans_get_avail_info(handle, &type, &dev_info);
#endif
}

/**
 * @name session init
 * @attention server api only
 * @details
 * 1. fid, sid check
 * 2. register session
 * 3. return session id
 */
/* Note: Implement performance optimization specifications by adding completion_init and establishing relationships
 * between each session and its corresponding completion.*/
int32_t ipc_trans_layer_stub_create_handle(const uint8_t endid,
					   const uint8_t fid, const uint8_t sid,
					   const uint8_t cid, uint8_t *handle)
{
	uint8_t ses_id = 0;
	int32_t ret = -1;
	if (endid != dev_info.g_ipc_pid)
			return -ERR_PID_IS_INVALID;
	// transfer endid to device info buffer address
	ret = ipc_trans_create_session(sid, fid, cid, MSGBX_SES_ROLE_SERVER,
				       &ses_id, &dev_info);
	if (ret < 0)
		return ret;
	*handle = ses_id;
#if !defined(BAREMETAL_VERSION_TRUNCATE)
	completion_init(&recv_comp[fid][sid]);
	session_exit[fid][sid] = false;
#endif
	return 0;
}

/**
 * @name send a reply message
 * @attention server api only
 */
int32_t ipc_trans_layer_stub_send_reply_msg(const uint8_t endid,
					    const uint8_t handle, serdes_t *msg)
{
	return ipc_trans_send_msg(handle, msg, MSGBX_MSG_TYPE_REPLY, &dev_info);
}

/**
 * @name send a signal message
 * @attention server api only
 */
int32_t ipc_trans_layer_stub_send_broadcast(const uint8_t endid,
					    const uint8_t handle, serdes_t *msg)
{
	return ipc_trans_send_msg(handle, msg, MSGBX_MSG_TYPE_BROADCAST,
				  &dev_info);
}

int32_t ipc_trans_layer_register_method(const uint8_t endid,
					const uint8_t handle, const uint8_t cmd)
{
#if (SESSION_COUNT > 1)
	return ipc_trans_register_method(handle, cmd, &dev_info);
#endif
	return 0;
}

/**
 * @name server unregister method
 * @attention server api only
 * @details
 * 1. fid, sid check
 * 2. register cmd in method map
 */
int32_t ipc_trans_layer_unregister_method(const uint8_t endid,
					  const uint8_t handle)
{
#if (SESSION_COUNT > 1)
	return ipc_trans_unregister_method(handle, &dev_info);
#endif
	return 0;
}

/**
 * @name receive method message
 * @attention server api only
 * @details
 */
int32_t ipc_trans_layer_stub_get_method_msg(const uint8_t endid,
					    const uint8_t handle, serdes_t *msg)
{
	return ipc_trans_get_msg(handle, MSGBX_MSG_TYPE_METHOD, msg, &dev_info);
}

/**
 * @name client session init
 * @attention client api only
 * @details
 * 1. fid, sid check
 * 2. register session
 * 3. return session id
 */
/* Note: Implement performance optimization specifications by adding completion_init and establishing relationships
 * between each session and its corresponding completion.*/
int32_t ipc_trans_layer_proxy_create_handle(const uint8_t endid,
					    const uint8_t fid,
					    const uint8_t sid,
					    const uint8_t cid, uint8_t *handle)
{
	uint8_t ses_id = 0;
	int32_t ret = -1;

	// only for debug mode
#if !defined(BAREMETAL_VERSION_TRUNCATE)
	completion_init(&recv_comp[fid][sid]);
	session_exit[fid][sid] = false;
#endif
	if (endid != dev_info.g_ipc_pid)
			return -ERR_PID_IS_INVALID;
	// transfer endid to device info buffer address
	ret = ipc_trans_create_session(sid, fid, cid, MSGBX_SES_ROLE_CLIENT,
				       &ses_id, &dev_info);
	if (ret < 0)
		return ret;
	*handle = ses_id;

	return 0;
}

/**
 * @name get a sinal message
 * @attention client api only
 * @details
 */
int32_t ipc_trans_layer_proxy_get_broadcast_msg(const uint8_t endid,
						const uint8_t handle,
						serdes_t *msg)
{
	return ipc_trans_get_msg(handle, MSGBX_MSG_TYPE_BROADCAST, msg,
				 &dev_info);
}

/**
 * @name send a method message
 * @attention client api only
 */
int32_t ipc_trans_layer_proxy_send_method(const uint8_t endid,
					  const uint8_t handle, serdes_t *msg)
{
	return ipc_trans_send_msg(handle, msg, MSGBX_MSG_TYPE_METHOD, &dev_info);
}

/**
 * @name get a reply message
 * @attention client api only
 * @details
 */
int32_t ipc_trans_layer_proxy_get_reply_msg(const uint8_t endid,
					    const uint8_t handle, serdes_t *msg)
{
	return ipc_trans_get_msg(handle, MSGBX_MSG_TYPE_REPLY, msg, &dev_info);
}

/**
 * @name handle destroy
 * @attention both client and server api
 * @details
 */
/* Note: Demo code. Upon session termination, we output its associated
 * diagnostic information.*/
int32_t ipc_trans_layer_destroy_handle(const uint8_t endid,
				       const uint8_t handle)
{
#ifdef DEBUG_MODE_ENABLE
	debug_info_t info;
	ipc_trans_get_debug_info(handle, &info, &dev_info);
	IPC_LOG_INFO(
		"session %u info role: %u, send_msg_cnt:%u, send_rw_msg_cnt:%u, "
		"send_fail_cnt:%u, last_msg_frame_cnt:%u ",
		handle, info.role, info.send_msg_cnt, info.send_rw_msg_cnt,
		info.send_fail_cnt, info.send_frame_cnt);
	IPC_LOG_INFO("recv_msg_1_cnt:%u, recv_msg_2_cnt:%u,recv_rw_msg_cnt:%u ",
		     info.recv_msg_1_cnt, info.recv_msg_2_cnt,info.recv_rw_msg_cnt);
	IPC_LOG_INFO(
		"send_start_time: %lu, send_end_time:%lu, collate_time:%lu, "
		"get_msg_time:%lu ",
		info.send_start_time, info.send_end_time, info.collate_time,
		info.get_msg_time);
#endif
	return ipc_trans_close_session(handle, &dev_info);
}

/* Note: To implement the performance optimization specification, this function is invoked when an application needs to
 * stop a receiving thread, thereby releasing any blocking on the receiving thread.*/
int32_t ipc_trans_layer_release_recv_wait(const uint8_t endid,
					  const uint8_t handle)
{
#if !defined(BAREMETAL_VERSION_TRUNCATE)
	uint8_t fid, sid;
	session_dist(handle, &sid, &fid);
	session_exit[fid][sid] = true;
	complete(&recv_comp[fid][sid]);
#endif
	return 0;
}

/**
 * @name session init
 * @attention fast path app only
 * @details
 * 1. fid, sid check
 * 2. register session
 * 3. return session id
 */
int32_t ipc_trans_layer_create_handle(const uint8_t endid, const uint8_t fid,
				      const uint8_t sid, uint8_t *handle)
{
	uint8_t ses_id = 0;
	int32_t ret = -1;

	// transfer endid to device info buffer address
	ret = ipc_trans_create_session(sid, fid, 0, MSGBX_SES_ROLE_FASTPATH,
				       &ses_id, &dev_info);
	if (ret < 0)
		return ret;
	*handle = ses_id;

#if !defined(BAREMETAL_VERSION_TRUNCATE)
	completion_init(&recv_comp[fid][sid]);
	session_exit[fid][sid] = false;
#endif

	return 0;
}

int32_t ipc_trans_layer_send_msg(const uint8_t endid, const uint8_t handle,
				 rw_msg_t *msg)
{
	return ipc_trans_send_rwmsg(handle, msg, &dev_info);
}

/**
 * @name receive message
 * @attention fast path app only
 * @details
 */
int32_t ipc_trans_layer_get_msg(const uint8_t endid, const uint8_t handle,
				const int32_t timeout, rw_msg_t *msg,
				uint64_t *timestamp)
{
#if defined(IPC_RTE_KERNEL)
	uint8_t fid = 0, sid = 0;
	int32_t ret = -1;

	if (session_isvalid(ses_id, dev_info) < 0)
		return -ERR_SES_IS_INVALID;

	session_dist(handle, &sid, &fid);
	if (timeout == -1)
		wait_for_completion(&recv_comp[fid][sid]);
	else {
		ret = wait_for_completion_timeout(&recv_comp[fid][sid],
						  msecs_to_jiffies(timeout));
		if (ret == 0)
			//timeout
			return -ERR_FASTPAH_GET_TIMEOUT;
	}

	ret = ipc_trans_get_rwmsg(handle, msg, timestamp, &dev_info);
	return ret;
#else
	return ipc_trans_get_rwmsg(handle, msg, timestamp, &dev_info);
#endif
}
